{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 高效的 tensorflow2\n",
    "tensorflow 做出了很多改变使得 tensorflow 使用者更加高效（地使用它），tensorflow2 移除了[冗余的 API](https://github.com/tensorflow/community/blob/master/rfcs/20180827-api-names.md)，使得 API 更加一致（统一 RNN，统一优化器），并且通过 [eager execution](https://tensorflow.google.cn/guide/eager) 与其余 python 运行更好地整合。\n",
    "可以通过 [RFCs](https://github.com/tensorflow/community/pulls?utf8=%E2%9C%93&q=is%3Apr) 看到 tensorflow 是如何演变为 2.0 的。本指南对 tensorflow2 进行一个概括性的描述。此时我是建立在你已经对 tensorflow1 一定熟悉的情况下进行描述的。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 重大变化的简要概述\n",
    "### API 清理\n",
    "在 tensorflow2 中许多原有的 API 被清除或者移动。一些主要的变化包括：移除 tf.app、tf.flag、tf.logging，支持现在开源的 absl-py，迁移 tf.contrib 下的项目（将其移动到 tensorflow-addons 中），通过将较少使用的函数移动到子包（比如 tf.math）中来减少 tf.* 命名空间的使用，一些 API 在 tensorflow2 中被替代-tf.summary、tf.keras.metrics、tf.keras.optimizers。\n",
    "\n",
    "### eager execution（急切性执行）\n",
    "在 tensorflow1 中，要求使用者调用 tf.* API 将抽象语法树手动拼接在一起，然后，它要求用户通过将一组输出张量和输入张量传递给 session.run（）调用来手动编译抽象语法树。tensorflow2 中使用 eager execution 将我们从中解放出来，我们可以像正常使用 python 一样进行编译，所有代码行均按顺序执行。\n",
    "\n",
    "### 不再需要全局化\n",
    "tensorflow1 严重依赖于隐式全局命名空间。当调用 tf.Variable() 时，该变量默认被放入图中，即使丢失了指向它的 python 变量的跟踪，它也会保留在图中。然后，我们可以通过创建该变量时的命名来恢复 tf.Variable。在此基础上有了很多 API 用于帮助用户找到其变量，比如 tf.get_global_step()、tf.global)variables_initializer() 之类的方法，而在 tensorflow2 中移除了这个机制，采用了跟踪变量，即如果丢失了 tf.Variable，那么该变量会被作为垃圾回收。\n",
    "\n",
    "### 函数代替会话\n",
    "对于 session.run()，我们通常是指定输入和要调用的函数，然后获取一组输出。在 tensorflow2 中，我们可以使用 tf.funcation() 修饰 python 函数以将其标记为 JIT（即时）编译，便于 tensorflow 将其作为单个图形运行。 该方法有以下好处：\n",
    "1. 性能优化：可以优化功能（节点修剪，内核融合等）\n",
    "2. 可移植性：可以导出/导入功能，从而允许用户重用和共享模块化TensorFlow功能。\n",
    "\n",
    ">有了这种自由穿插 python 和 tensorflow2 代码的功能，使用者可以利用 python 的语法特点。但是可移植的 tensorflow2 无法在没有 python 编译器的情况下执行，比如 C++、JavaScript。为了帮助使用者避免在添加了 tf.funcation() 后还要写重复功能的代码，AutoGraph 将 Python 构造的子集转换为它们的TensorFlow等效项：\n",
    "    1. for/while -> tf.while_loop（同样支持break 和 continue）\n",
    "    2. if -> tf.cond\n",
    "    3. for _ in dataset -> dataset.reduce\n",
    "AutoGraph 支持控制流的任意嵌套，这使得可以高效，简洁地实现许多复杂的 ML 程序，例如序列模型，强化学习，自定义训练循环等。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 对使用 tensorflow2 的建议\n",
    "### 将你的代码重构为较小的函数\n",
    "在 tensorflow1 中常见的使用模型是（厨房水槽）策略，该策略先占所有可能计算的并集，然后通过 session.run() 评估选定的张量。在 tensorflow2 中，使用者应该将代码重构为较小的函数，并且根据需要调用函数。`通常，没有必要用 tf.function 修饰每个较小的功能，仅使用 tf.funcation 修饰高级计算，例如：训练的一个步骤或者模型的钱箱传播`\n",
    "### 使用 Keras 图层和模型去管理变量\n",
    "Keras 模型和图层提供了方便的变量和 train_variable 属性，这些以递归方式收集所有因变量。这使得在使用变量的地方本地管理变量变得容易很多。像以下代码对比\n",
    "```python\n",
    "def dense(x, W, b):\n",
    "  return tf.nn.sigmoid(tf.matmul(x, W) + b)\n",
    "\n",
    "@tf.function\n",
    "def multilayer_perceptron(x, w0, b0, w1, b1, w2, b2 ...):\n",
    "  x = dense(x, w0, b0)\n",
    "  x = dense(x, w1, b1)\n",
    "  x = dense(x, w2, b2)\n",
    "\n",
    "'''使用 Keras'''\n",
    "layers = [tf.keras.layers.Dense(hidden_size, activation=tf.nn.sigmoid) for _ in range(n)]\n",
    "perceptron = tf.keras.Sequential(layers)\n",
    "```\n",
    "Keras 图层/模型继承 tf.train.Checkpointable 并与 @tf.function 集成在一起，这使得我们可以直接从 Keras 对象中设置检查点和导出 SavedModel。\n",
    "\n",
    "### 组合使用 tf.data.Dataset 和 @tf.funcation\n",
    "当迭代适合内存的训练数据集时，要记得使用常规的 python 迭代，tf.data.Dataset 是从磁盘流式传输训练数据的最佳方法。`数据集是可迭代的，而不是迭代器`，例如下面：\n",
    "```python\n",
    "@tf.function\n",
    "def train(model, dataset, optimizer):\n",
    "  for x, y in dataset:\n",
    "    with tf.GradientTape() as tape:\n",
    "      prediction = model(x, training=True)\n",
    "      loss = loss_fn(prediction, y)\n",
    "    gradients = tape.gradient(loss, model.trainable_variables)\n",
    "    optimizer.apply_gradients(zip(gradients, model.trainable_variables))\n",
    "```\n",
    "`当我们使用 Keras 的 fit() 时，不用当心数据集的迭代问题，Keras 内部帮我们进行了处理`:\n",
    "```pyton\n",
    "model.compile(optimizer=optimizer, loss=loss_fn)\n",
    "model.fit(dataset)\n",
    "```\n",
    "\n",
    "### 利用带有控制流的 AutoGraph\n",
    "AutoGraph提供了一种将依赖于数据的控制流转换为图形模式等效项（例如tf.cond和tf.while_loop）的方法，进一步可查看[Better performance with tf.function and AutoGraph\n",
    "](https://tensorflow.google.cn/guide/function)。其引用方式如下：\n",
    "```python\n",
    "from tensorflow import contrib\n",
    "autograph = contrib.autograph\n",
    "```\n",
    "\n",
    "### tf.metrics汇总数据，tf.summary记录数据\n",
    "要记录摘要，那么应该使用 tf.summary。然后使用上下文管理器将其重定向到编写器（如果忽略上下文编写器，那么什么都不会发生）：\n",
    "```python\n",
    "summary_writer = tf.summary.create_file_writer('/tmp/summaries')\n",
    "with summary_writer.as_default():\n",
    "  tf.summary.scalar('loss', 0.1, step=42)\n",
    "```\n",
    "在将数据记录之前要先汇总，使用 tf.metrics，指标是有状态的；它们将值累加后在你调用 .result() 的时候返回结果，可以使用 .reset_states 清除累加值：\n",
    "```python\n",
    "def train(model, optimizer, dataset, log_freq=10):\n",
    "  avg_loss = tf.keras.metrics.Mean(name='loss', dtype=tf.float32)\n",
    "  for images, labels in dataset:\n",
    "    loss = train_step(model, optimizer, images, labels)\n",
    "    avg_loss.update_state(loss)\n",
    "    if tf.equal(optimizer.iterations % log_freq, 0):\n",
    "      tf.summary.scalar('loss', avg_loss.result(), step=optimizer.iterations)\n",
    "      avg_loss.reset_states()\n",
    "\n",
    "def test(model, test_x, test_y, step_num):\n",
    "  loss = loss_fn(model(test_x, training=False), test_y)\n",
    "  tf.summary.scalar('loss', loss, step=step_num)\n",
    "\n",
    "train_summary_writer = tf.summary.create_file_writer('/tmp/summaries/train')\n",
    "test_summary_writer = tf.summary.create_file_writer('/tmp/summaries/test')\n",
    "\n",
    "with train_summary_writer.as_default():\n",
    "  train(model, optimizer, dataset)\n",
    "\n",
    "with test_summary_writer.as_default():\n",
    "  test(model, test_x, test_y, optimizer.iterations)\n",
    "```\n",
    "\n",
    "可以通过将TensorBoard指向摘要日志目录来可视化生成的摘要：\n",
    "```sh\n",
    "tensorboard --logdir /tmp/summaries\n",
    "```\n",
    "\n",
    "### 调试时使用 tf.config.experimental_run_functions_eagerly()\n",
    "在 tensorflow2 中，eager 执行可以让我们逐步运行代码以检查形状、数据类型和值。某些 API 被设计为使用 graph 执行，以提高性能和可移植性。调试时使用 tf.config.experimental_run_functions_eagerly（True）。experimental_run_functions_eagerly 的作用是启用/禁用tf.functions的急切执行。例如，我实现了一个简单的迭代sqrt函数，并且想要收集中间值并绘制收敛性。将值追加到@ tf.function中的列表通常将不起作用，因为它只会记录要跟踪的张量，而不是值。相反，您可以执行以下操作。\n",
    "```sh\n",
    "ys = [] \n",
    " \n",
    "@tf.function \n",
    "def sqrt(x): \n",
    "  y = x / 2 \n",
    "  d = y \n",
    "  for _ in range(10): \n",
    "    d /= 2 \n",
    "    if y * y < x: \n",
    "      y += d \n",
    "    else: \n",
    "      y -= d \n",
    "    ys.append(y.numpy()) \n",
    "  return y \n",
    " \n",
    "tf.config.experimental_run_functions_eagerly(True) \n",
    "sqrt(tf.constant(2.)) \n",
    "\n",
    "ys \n",
    "\n",
    "\n",
    "tf.config.experimental_run_functions_eagerly(False) \n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "> <ipython-input-1-8d9b312a2822>(7)f()\n",
      "-> x = x + 1\n",
      "(Pdb) 3\n",
      "3\n"
     ]
    }
   ],
   "source": [
    "import tensorflow as tf\n",
    "@tf.function\n",
    "def f(x):\n",
    "  if x > 0:\n",
    "    import pdb\n",
    "    pdb.set_trace()\n",
    "    x = x + 1\n",
    "  return x\n",
    "\n",
    "tf.config.experimental_run_functions_eagerly(True)\n",
    "f(tf.constant(1))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6rc1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
